package com.mxchip.livestarmobile.mobile.util.Camera;

import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;


import com.libyuv.util.YuvUtil;
import com.mxchip.livestarmobile.mobile.ui.call.AVDevice;
import com.mxchip.hxb3288a.av.player.PreviewOnSurfaceCreatedHandler;

import java.io.IOException;
import java.util.List;

public class CameraHandle implements PreviewOnSurfaceCreatedHandler {
    private static final String TAG = "CameraHandle";

    public static final String DEVICE_LOCAL_NAME = AVDevice.CAMERA_LOCAL;
    private static final int REQ_CAMERA_WIDTH =1280;// 1280;
    private static final int REQ_CAMERA_HEIGHT = 720;//720;
    private static final int REQ_CAMERA_FPS = 30;
    private static final int REQ_CAMERA_ENCODE_BITS = 1024 * 1024 * 3/2 ;// * 3 / 2;

    public static final String DEVICE_SMALL_NAME = AVDevice.CAMERA_LOCAL_SMALL;
    private static final int ENCODER_SMALL_VIDEO_WIDTH = 640;
    private static final int ENCODER_SMALL_VIDEO_HEIGHT = 480;
    private static final int ENCODER_SMALL_RATE = 25;
    private static final int ENCODER_SMALL_BIT_RATE = 1024 * 512;

    private boolean isCameraOn = true;
    private static CameraHandle mInstance = new CameraHandle();

    public static synchronized CameraHandle getInstance() {
        return mInstance;
    }

    public boolean isCameraOn() {
        return isCameraOn;
    }

    public void setCameraOn(boolean cameraOn) {
        isCameraOn = cameraOn;
    }

    Camera mCamera;

    public boolean openCamera() {
        int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        mCamera = safeCameraOpen(cameraId);
//        if (mCamera == null){
//            Appli.setCameraId(CameraId.INTERNAL);
//            cameraId = Appli.getCameraId();
//            mCamera = safeCameraOpen(cameraId );
//        }
        if (mCamera == null) {

            return false;
            //throw new RuntimeException("Unable to open camera");
        }
        isCameraOn = true;
        setCameraDisplayOrientation(mCamera, cameraId);
        Camera.Parameters params = mCamera.getParameters();
        List<String> focusModes = params.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            // set the focus mode
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        CameraUtils.choosePreviewSize(params, REQ_CAMERA_WIDTH, REQ_CAMERA_HEIGHT);
        CameraUtils.chooseFixedPreviewFps(params, REQ_CAMERA_FPS * 1000);
        params.setPreviewFormat(ImageFormat.NV21);
        // set Camera parameters
        mCamera.setParameters(params);
        YuvUtil.init(REQ_CAMERA_WIDTH, REQ_CAMERA_HEIGHT, ENCODER_SMALL_VIDEO_WIDTH, ENCODER_SMALL_VIDEO_HEIGHT);

        return true;
    }


    private Camera safeCameraOpen(int cameraId) {
        Camera camera = null;
        try {
            camera = Camera.open(cameraId);
            if (camera == null) {
                Log.d(TAG, "No front-facing camera found; opening default");
                camera = Camera.open(cameraId);    // opens first back-facing camera
            }
            if (camera == null) {
                throw new RuntimeException("Unable to open camera");
            }
        } catch (Exception e) {
            Log.e(TAG, "Failed to open Camera");
            e.printStackTrace();
        }
        return camera;
    }

    /**
     * Compensate for the camera angle and set it to mirror image because
     * that's what people expect.
     */
    private void setCameraDisplayOrientation(Camera mCamera, int cameraId) {
        if (mCamera == null) {
            Log.d(TAG, "Cannot set orientation. mCamera is null.");
            throw new RuntimeException("Unable to open camera");
        }

        Camera.CameraInfo info =
                new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = 0;
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        mCamera.setDisplayOrientation(result);
    }


    public void setPreviewTexture(SurfaceTexture surfaceTexture) {
        try {
            stopPreview();
            if (mCamera == null) {
                throw new NullPointerException("mCamera is null");
            }
            mCamera.setPreviewTexture(surfaceTexture);
            startPreview(mCamera);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void startPreview(Camera mCamera) {
        if (mCamera != null) {
            mCamera.setPreviewCallback(new MyPreviewCallback());
            mCamera.startPreview();
            initEncode();
            initEncode2();
        }
    }

    private AvcEncoder avcCodec = null;

    private void initEncode() {

        avcCodec = new AvcEncoder(REQ_CAMERA_WIDTH, REQ_CAMERA_HEIGHT, REQ_CAMERA_FPS,
                REQ_CAMERA_ENCODE_BITS, 5);
        avcCodec.setEcodeH264Callback(cameraCallback);
    }

    private byte[] dstData = new byte[ENCODER_SMALL_VIDEO_WIDTH * ENCODER_SMALL_VIDEO_HEIGHT * 3 / 2];
    private byte[] nv21Data = new byte[ENCODER_SMALL_VIDEO_WIDTH * ENCODER_SMALL_VIDEO_HEIGHT * 3 / 2];

    class MyPreviewCallback implements Camera.PreviewCallback {

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            if (mCamera == null) {
                return;
            }

            if (avcCodec2 != null) {
                YuvUtil.compressYUV(data, REQ_CAMERA_WIDTH, REQ_CAMERA_HEIGHT, dstData, ENCODER_SMALL_VIDEO_WIDTH, ENCODER_SMALL_VIDEO_HEIGHT, 0, 270, false, false);
                YuvUtil.yuvI420ToNV21(dstData, nv21Data, ENCODER_SMALL_VIDEO_WIDTH, ENCODER_SMALL_VIDEO_HEIGHT);
                avcCodec2.encodeFrame(nv21Data, false);
            }
            if (avcCodec != null)
                avcCodec.encodeFrame(data, false);


        }
    }

    private AvcEncoder.EcodeH264Callback cameraCallback = new AvcEncoder.EcodeH264Callback() {

        @Override
        public void getH264Data(byte[] data, int start, int length, boolean isSharing, boolean isKey) {
            if (mCameraDataCallback != null && isCameraOn) {
                mCameraDataCallback.onH264FramePacket(data, DEVICE_LOCAL_NAME, REQ_CAMERA_WIDTH, REQ_CAMERA_HEIGHT, 0, System.currentTimeMillis());
                //mCameraDataCallback.onH264FramePacket(data, DEVICE_SMALL_NAME, REQ_CAMERA_WIDTH, REQ_CAMERA_HEIGHT, 0, System.currentTimeMillis());
            }
        }
    };

    private EncoderCallback mCameraDataCallback;

    public void setEncoderCallback(EncoderCallback cameraDataCallback) {
        mCameraDataCallback = cameraDataCallback;
        if (cameraDataCallback == null) {
            avcCodec.StopThread();
            avcCodec2.StopThread();
        }
    }


    private AvcEncoder avcCodec2 = null;

    private void initEncode2() {

        avcCodec2 = new AvcEncoder(ENCODER_SMALL_VIDEO_WIDTH, ENCODER_SMALL_VIDEO_HEIGHT, ENCODER_SMALL_RATE,
                ENCODER_SMALL_BIT_RATE, 5);
        avcCodec2.setEcodeH264Callback(cameraCallback2);
    }

    private AvcEncoder.EcodeH264Callback cameraCallback2 = new AvcEncoder.EcodeH264Callback() {

        @Override
        public void getH264Data(byte[] data, int start, int length, boolean isSharing, boolean isKey) {
            if (mCameraDataCallback != null && isCameraOn) {
                mCameraDataCallback.onH264FramePacket(data, DEVICE_SMALL_NAME, ENCODER_SMALL_VIDEO_WIDTH, ENCODER_SMALL_VIDEO_HEIGHT, 0, System.currentTimeMillis());
            }
        }
    };


    private void stopPreview() {
        if (mCamera != null) {
            try {
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview();
            } catch (Exception e) {
                Log.e(TAG, "Tried to stop a non-existant preview. Ignoring");
                e.printStackTrace();
            }
        }
    }

    public void stopCamera() {
        if (mCamera != null) {
            isCameraOn = false;
            mCameraDataCallback = null;
            this.stopPreview();
            mCamera.release();
            mCamera = null;
        }

    }



    public boolean isCameraOpen() {
        return mCamera != null;
    }

    public void setEncoderBitRate(int bitrate) {
        if (avcCodec != null) {
            avcCodec.changeBitRate(bitrate);
        }
    }

     public void setEncoder2BitRate(int bitrate) {
        if (avcCodec2 != null) {
            avcCodec2.changeBitRate(bitrate);
        }
    }

    public void applyIFrame() {
        if (avcCodec != null) {
            avcCodec.applyIFrame();
        }

        if (avcCodec2 != null) {
            avcCodec2.applyIFrame();
        }
    }
}
